/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
 * Description: Hmfs-tools mkfs format
 */

#include "mkfs_format.h"

#include <cinttypes>
#include <cmath>
#include <memory>
#include <string>
#include <sys/types.h>

#include "check_point_writer.h"
#include "device_manager.h"
#include "hmfs_common.h"
#include "hmfs_encoding.h"
#include "hmfs_io.h"
#include "hmfs_log.h"
#include "hmfs_zoned.h"
#include "main_writer.h"
#include "mkfs_command_parser.h"
#include "nat_writer.h"
#include "securec.h"
#include "sit_writer.h"
#include "super_block_writer.h"

int32_t main(int32_t argc, char *argv[])
{
    using namespace OHOS::Hmfs;
    ArgParseResult ret = MkfsCmdParser::GetInstance().Parse(argc, argv);
    if (ret == ArgParseResult::FINISH) {
        return 0;
    } else if (ret == ArgParseResult::ERROR) {
        return -1;
    }

    MkfsFormat mkfs(MkfsCmdParser::GetInstance().GetCmdConfig());
    return mkfs.Process();
}

namespace OHOS {
namespace Hmfs {
MkfsFormat::MkfsFormat(CmdConfig &cfgPara) : cfgPara_(cfgPara) {}

int32_t MkfsFormat::Process()
{
    if (CheckDevicesMounted() != 0) {
        HMFS_ERROR("Not available on mounted device.");
        return -1;
    }

    if (CreateDeviceInfo() != 0) {
        return -1;
    }

    if (OverwriteDevices() != 0) {
        return -1;
    }

    if (CalcHmfsData() != 0) {
        return -1;
    }

    if (cfgPara_.trim && DeviceManager::GetInstance().TrimDevices()) {
        return -1;
    }

    if (Format() != 0) {
        HMFS_ERROR("Failed to format the device.");
        return -1;
    }

    HMFS_INFO("Format successful.");
    return 0;
}

int32_t MkfsFormat::GetDeviceSectorInfo()
{
    uint32_t segmentSize = HMFS_BLOCK_SIZE * BLOCKS_PER_SEGMENT;

    hmfsData_.sectorCount = DeviceManager::GetInstance().GetTotalSectors();
    for (uint32_t i = 0; i < cfgPara_.deviceList.size(); i++) {
        DeviceInfo *deviceInfo = DeviceManager::GetInstance().GetDeviceInfo(i);
        HMFS_ASSERT(deviceInfo != nullptr);

        if (i == 0) {
            hmfsData_.sectorSize = deviceInfo->sectorSize;
            hmfsData_.sectorsPerBlk = HMFS_BLOCK_SIZE / hmfsData_.sectorSize;

            if (cfgPara_.wantedSectorCount < hmfsData_.sectorCount) {
                HMFS_INFO("total device sectors = %" PRIu64 " (in %u bytes)\n",
                    hmfsData_.sectorCount, hmfsData_.sectorSize);
                hmfsData_.sectorCount = cfgPara_.wantedSectorCount;
                deviceInfo->sectorCount = hmfsData_.sectorCount;
            }
            if (hmfsData_.sectorCount * hmfsData_.sectorSize > HMFS_MAX_DISK_SIZE) {
                HMFS_ERROR("HMFS can support 16TB at most.");
                return -1;
            }

            if (deviceInfo->sectorCount * deviceInfo->sectorSize < hmfsData_.zoneAlignStartOffset) {
                HMFS_ERROR("Device size is not sufficient for HMFS volume.");
                return -1;
            }

            deviceInfo->segmentCount =
                (deviceInfo->sectorCount * hmfsData_.sectorSize - hmfsData_.zoneAlignStartOffset) / segmentSize;
            deviceInfo->startBlockId = 0;
            deviceInfo->endBlockId = deviceInfo->segmentCount * BLOCKS_PER_SEGMENT - 1 + hmfsData_.segment0BlockId;
        } else {
            DeviceInfo *prevDevice = DeviceManager::GetInstance().GetDeviceInfo(i - 1);
            HMFS_ASSERT(prevDevice != nullptr);

            deviceInfo->segmentCount = deviceInfo->sectorCount / (hmfsData_.sectorsPerBlk * BLOCKS_PER_SEGMENT);
            deviceInfo->startBlockId = prevDevice->endBlockId + 1;
            deviceInfo->endBlockId = deviceInfo->startBlockId + deviceInfo->segmentCount * BLOCKS_PER_SEGMENT - 1;
        }

        hmfsData_.segmentCount += deviceInfo->segmentCount;
    }

    if (hmfsData_.segmentCount < HMFS_MIN_SEGMENT_COUNT) {
        HMFS_ERROR("Device size is not sufficient for HMFS volume.");
        return -1;
    }

    return 0;
}

int32_t MkfsFormat::GetZoneInfo()
{
    hmfsData_.zonedModel = HMFS_ZONED_NONE;
    for (uint32_t id = 0; id < cfgPara_.deviceList.size(); id++) {
        DeviceInfo *deviceInfo = DeviceManager::GetInstance().GetDeviceInfo(id);
        if (deviceInfo && (deviceInfo->zonedModel > hmfsData_.zonedModel)) {
            hmfsData_.zonedModel = deviceInfo->zonedModel;
        }
    }

    if ((hmfsData_.zonedModel != HMFS_ZONED_NONE) && !cfgPara_.zonedMode) {
        HMFS_ERROR("Zoned block device feature is required.");
        return -1;
    }

    if (hmfsData_.zonedModel != HMFS_ZONED_NONE) {
        for (uint32_t id = 0; id < cfgPara_.deviceList.size(); id++) {
            DeviceInfo *deviceInfo = DeviceManager::GetInstance().GetDeviceInfo(id);
            if ((deviceInfo == nullptr) || (deviceInfo->zonedModel == HMFS_ZONED_NONE)) {
                continue;
            }

            if (id == 0) {
                hmfsData_.blocksPerZone = deviceInfo->zoneBlocks;
            } else if (deviceInfo->zoneBlocks != hmfsData_.blocksPerZone) {
                HMFS_ERROR("zones of different size are not supported");
                return -1;
            }
        }

        cfgPara_.segsPerSection = hmfsData_.blocksPerZone / BLOCKS_PER_SEGMENT;
        cfgPara_.sectionsPerZone = 1;
    } else {
        if (cfgPara_.zonedMode) {
            HMFS_ERROR("Device may not be a zoned block");
            return -1;
        }
    }

    hmfsData_.segsPerZone = cfgPara_.segsPerSection * cfgPara_.sectionsPerZone;

    HMFS_INFO("Segments per section = %d", cfgPara_.segsPerSection);
    HMFS_INFO("Sections per zone = %d", cfgPara_.sectionsPerZone);
    HMFS_INFO("sector size = %u", hmfsData_.sectorSize);
    HMFS_INFO("total sectors = %" PRIu64 " (%" PRIu64 " MB)", hmfsData_.sectorCount,
        HmfsBytesToMb(hmfsData_.sectorCount * hmfsData_.sectorSize));
    return 0;
}

void MkfsFormat::ClacBlockSize()
{
    uint32_t sitBlockCount = AlignUpCount(hmfsData_.zoneAlignedSegCount, SIT_ENTRIES_PER_BLOCK);
    hmfsData_.segmentCountInSIT = AlignUpCount(sitBlockCount, BLOCKS_PER_SEGMENT) * HMFS_SIT_COUNT;

    hmfsData_.natStartBlkId = hmfsData_.sitStartBlkId + hmfsData_.segmentCountInSIT * BLOCKS_PER_SEGMENT;

    uint32_t validBlksRemaind =
        (hmfsData_.zoneAlignedSegCount - hmfsData_.segmentCountInCP - hmfsData_.segmentCountInSIT) * BLOCKS_PER_SEGMENT;
    uint32_t natBlockCount = AlignUpCount(validBlksRemaind, NAT_ENTRIES_PER_BLOCK);

    uint32_t sitBitmapSize = (hmfsData_.segmentCountInSIT / HMFS_SIT_COUNT) * BLOCKS_PER_SEGMENT / BITS_PER_BYTE;
    uint32_t sitMaxBitmapSize = (sitBitmapSize > SIT_MAX_BITMAP_SIZE) ? SIT_MAX_BITMAP_SIZE : sitBitmapSize;

    if (cfgPara_.largeNatBitmap) {
        uint32_t natSegments = AlignUpCount(natBlockCount, BLOCKS_PER_SEGMENT) * DEFAULT_NAT_ENTRY_RATIO /
            PERCENT_TIMES;
        hmfsData_.segmentCountInNAT = natSegments ? natSegments : 1;
        uint32_t natMaxBitmapSize = hmfsData_.segmentCountInNAT * BLOCKS_PER_SEGMENT / BITS_PER_BYTE;

        if (sitMaxBitmapSize + natMaxBitmapSize > CP_MAX_BITMAP_SIZE) {
            uint32_t diff = sitMaxBitmapSize + natMaxBitmapSize - CP_MAX_BITMAP_SIZE;
            hmfsData_.checkPointPayload = AlignUpCount(diff, HMFS_BLOCK_SIZE);
        } else {
            hmfsData_.checkPointPayload = 0;
        }
    } else {
        uint32_t natMaxBitmapSize;
        if (sitMaxBitmapSize > CP_MAX_SIT_BITMAP_SIZE) {
            natMaxBitmapSize = CP_MAX_BITMAP_SIZE;
            hmfsData_.checkPointPayload = AlignUpCount(sitMaxBitmapSize, HMFS_BLOCK_SIZE);
        } else {
            natMaxBitmapSize = CP_MAX_BITMAP_SIZE - sitMaxBitmapSize;
            hmfsData_.checkPointPayload = 0;
        }

        uint32_t natMaxSegments = (natMaxBitmapSize * BITS_PER_BYTE) / BLOCKS_PER_SEGMENT;
        hmfsData_.segmentCountInNAT = AlignUpCount(natBlockCount, BLOCKS_PER_SEGMENT);
        if (hmfsData_.segmentCountInNAT > natMaxSegments) {
            hmfsData_.segmentCountInNAT = natMaxSegments;
        }
    }
    hmfsData_.segmentCountInNAT *= HMFS_NAT_COUNT;

    validBlksRemaind -= hmfsData_.segmentCountInNAT * BLOCKS_PER_SEGMENT;
    uint32_t ssaBlockCount = (cfgPara_.features & HMFS_FEATURE_RO) ? 0 : (validBlksRemaind / BLOCKS_PER_SEGMENT + 1);
    hmfsData_.segmentCountInSSA = AlignUpCount(ssaBlockCount, BLOCKS_PER_SEGMENT);

    uint32_t metaSegmentCount = hmfsData_.segmentCountInCP + hmfsData_.segmentCountInSIT +
        hmfsData_.segmentCountInNAT + hmfsData_.segmentCountInSSA;
    uint32_t remainder = metaSegmentCount % hmfsData_.segsPerZone;
    if (remainder) {
        hmfsData_.segmentCountInSSA += hmfsData_.segsPerZone - remainder;
    }

    uint64_t metaZoneCount = AlignUpCount(metaSegmentCount, hmfsData_.segsPerZone);
    hmfsData_.mainStartBlkId = hmfsData_.segment0BlockId + metaZoneCount * hmfsData_.segsPerZone * BLOCKS_PER_SEGMENT;
}

bool MkfsFormat::DealZoneModel()
{
    if (!cfgPara_.zonedMode) {
        return true;
    }
    uint32_t mainBlkZone = hmfsData_.mainStartBlkId / hmfsData_.blocksPerZone;
    for (uint32_t id = 0; id < cfgPara_.deviceList.size(); id++) {
        DeviceInfo *deviceInfo = DeviceManager::GetInstance().GetDeviceInfo(id);
        if (id == 0) {
            if ((deviceInfo->zonedModel == HMFS_ZONED_HM) && (deviceInfo->randomZonesCount < mainBlkZone)) {
                HMFS_ERROR("Device does not have enough random write zones (%u needed)", mainBlkZone);
                return false;
            }
        } else if ((deviceInfo->zonedModel != HMFS_ZONED_NONE) &&
            (deviceInfo->startBlockId < hmfsData_.mainStartBlkId)) {
            HMFS_ERROR("Conventional device is too small, %" PRIu64 " MiB needed.",
                HmfsBlksToMb(hmfsData_.mainStartBlkId - deviceInfo->startBlockId));
            return false;
        }
    }
    return true;
}

int32_t MkfsFormat::CalcHmfsDataEnd()
{
    uint32_t metaSegmentCount = hmfsData_.segmentCountInCP + hmfsData_.segmentCountInSIT +
        hmfsData_.segmentCountInNAT + hmfsData_.segmentCountInSSA;
    uint64_t metaZoneCount = AlignUpCount(metaSegmentCount, hmfsData_.segsPerZone);
    uint32_t mainZoneCount = hmfsData_.zoneAlignedSegCount / hmfsData_.segsPerZone - metaZoneCount;
    if (mainZoneCount == 0) {
        HMFS_ERROR("device size is not sufficient for HMFS volume");
        return -1;
    }

    hmfsData_.sectionCount = mainZoneCount * cfgPara_.sectionsPerZone;
    hmfsData_.segmentCountInMain = hmfsData_.sectionCount * cfgPara_.segsPerSection;

    uint32_t availZones = (cfgPara_.features & HMFS_FEATURE_RO) ? HMFS_META_SEGMENTS_RO : HMFS_META_SEGMENTS;
    if (mainZoneCount <= availZones) {
        HMFS_ERROR("%d zones: Need more zones by shrinking zone size", mainZoneCount);
        return -1;
    }

    if (cfgPara_.features & HMFS_FEATURE_RO) {
        hmfsData_.currentSegments[CURSEG_NODE_HOT] = LastSection(LastZone(mainZoneCount));
        hmfsData_.currentSegments[CURSEG_NODE_WARM] = 0;
        hmfsData_.currentSegments[CURSEG_NODE_COLD] = 0;
        hmfsData_.currentSegments[CURSEG_DATA_HOT] = 0;
        hmfsData_.currentSegments[CURSEG_DATA_COLD] = 0;
        hmfsData_.currentSegments[CURSEG_DATA_WARM] = 0;
    } else if (cfgPara_.heapBasedAllocation) {
        hmfsData_.currentSegments[CURSEG_NODE_HOT] = LastSection(LastZone(mainZoneCount));
        hmfsData_.currentSegments[CURSEG_NODE_WARM] = PreviousZone(CURSEG_NODE_HOT);
        hmfsData_.currentSegments[CURSEG_NODE_COLD] = PreviousZone(CURSEG_NODE_WARM);
        hmfsData_.currentSegments[CURSEG_DATA_HOT] = PreviousZone(CURSEG_NODE_COLD);
        hmfsData_.currentSegments[CURSEG_DATA_COLD] = 0;
        hmfsData_.currentSegments[CURSEG_DATA_WARM] = NextZone(CURSEG_DATA_COLD);
    } else if (cfgPara_.zonedMode) {
        hmfsData_.currentSegments[CURSEG_NODE_HOT] = 0;
        hmfsData_.currentSegments[CURSEG_NODE_WARM] = NextZone(CURSEG_NODE_HOT);
        hmfsData_.currentSegments[CURSEG_NODE_COLD] = NextZone(CURSEG_NODE_WARM);
        hmfsData_.currentSegments[CURSEG_DATA_HOT] = NextZone(CURSEG_NODE_COLD);
        hmfsData_.currentSegments[CURSEG_DATA_WARM] = NextZone(CURSEG_DATA_HOT);
        hmfsData_.currentSegments[CURSEG_DATA_COLD] = NextZone(CURSEG_DATA_WARM);
    } else {
        hmfsData_.currentSegments[CURSEG_NODE_HOT] = 0;
        hmfsData_.currentSegments[CURSEG_NODE_WARM] = NextZone(CURSEG_NODE_HOT);
        hmfsData_.currentSegments[CURSEG_NODE_COLD] = NextZone(CURSEG_NODE_WARM);
        hmfsData_.currentSegments[CURSEG_DATA_HOT] = NextZone(CURSEG_NODE_COLD);
        hmfsData_.currentSegments[CURSEG_DATA_COLD] = std::max(LastZone(mainZoneCount >> CURRENT_COLD_DATA_ZONE_SHIFT),
            NextZone(CURSEG_DATA_HOT));
        hmfsData_.currentSegments[CURSEG_DATA_WARM] = std::max(LastZone(mainZoneCount >> 1),
            NextZone(CURSEG_DATA_COLD));
    }
    VerifyCurSegData();

    return HmfsCommon::GetInstance().ReadKernelVersion(hmfsData_.version, sizeof(hmfsData_.version));
}

int32_t MkfsFormat::CalcHmfsData()
{
    uint32_t segmentSize = HMFS_BLOCK_SIZE * BLOCKS_PER_SEGMENT;
    uint32_t zoneSize = cfgPara_.sectionsPerZone * cfgPara_.segsPerSection * segmentSize;

    if (cfgPara_.features & HMFS_FEATURE_RO) {
        hmfsData_.zoneAlignStartOffset = HMFS_BLOCK_SIZE * HMFS_SUPER_BLOCK_COUNT;
    } else {
        hmfsData_.zoneAlignStartOffset = AlignUpCount(HMFS_BLOCK_SIZE * HMFS_SUPER_BLOCK_COUNT, zoneSize) * zoneSize;
    }

    if (cfgPara_.zonedMode && cfgPara_.deviceList.size() > 1) {
        DeviceInfo *deviceInfo = DeviceManager::GetInstance().GetDeviceInfo(0);
        if (deviceInfo != nullptr) {
            hmfsData_.zoneAlignStartOffset += (deviceInfo->sectorCount * deviceInfo->sectorSize) % zoneSize;
        }
    }

    hmfsData_.segment0BlockId = hmfsData_.zoneAlignStartOffset / HMFS_BLOCK_SIZE;
    HMFS_INFO("zone aligned segment0 blkaddr = %u", hmfsData_.segment0BlockId);

    if (GetDeviceSectorInfo() != 0) {
        return -1;
    }

    if (GetZoneInfo() != 0) {
        return -1;
    }

    if (cfgPara_.zonedMode &&
        ((cfgPara_.deviceList.size() == 1 && hmfsData_.segment0BlockId % hmfsData_.blocksPerZone) ||
        (cfgPara_.deviceList.size() > 1 &&
        DeviceManager::GetInstance().GetDeviceInfo(1)->startBlockId % hmfsData_.blocksPerZone))) {
        HMFS_ERROR("Unaligned segment0 block address %u", hmfsData_.segment0BlockId);
        return -1;
    }

    hmfsData_.zoneAlignedSegCount = hmfsData_.segmentCount / hmfsData_.segsPerZone * hmfsData_.segsPerZone;
    hmfsData_.sitStartBlkId = hmfsData_.segment0BlockId + hmfsData_.segmentCountInCP * BLOCKS_PER_SEGMENT;

    ClacBlockSize();

    if (!DealZoneModel()) {
        return -1;
    }

    return CalcHmfsDataEnd();
}

void MkfsFormat::VerifyCurSegData(void)
{
    if (cfgPara_.features & HMFS_FEATURE_RO) {
        return;
    }

    bool needReorder = false;
    for (int32_t i = 0; i < CURSEG_TYPE_MAX; i++) {
        for (int32_t j = i + 1; j < CURSEG_TYPE_MAX; j++) {
            if (hmfsData_.currentSegments[i] == hmfsData_.currentSegments[j]) {
                needReorder = true;
                break;
            }
        }
    }

    if (needReorder) {
        hmfsData_.currentSegments[0] = 0;
        for (int32_t i = 1; i < CURSEG_TYPE_MAX; i++) {
            hmfsData_.currentSegments[i] = NextZone(i - 1);
        }
    }
}

int32_t MkfsFormat::CreateDeviceInfo()
{
    for (size_t i = 0; i < cfgPara_.deviceList.size(); i++) {
        if (DeviceManager::GetInstance().CreateDeviceInfo(cfgPara_.deviceList[i], i == 0)) {
            return -1;
        }
    }

    return 0;
}

int32_t MkfsFormat::CheckDevicesMounted()
{
    for (size_t i = 0; i < cfgPara_.deviceList.size(); i++) {
        if (DeviceManager::GetInstance().IsDeviceMounted(cfgPara_.deviceList[i])) {
            HMFS_DEBUG("Device mounted.");
            return -1;
        }
    }

    return 0;
}

int32_t MkfsFormat::OverwriteDevices()
{
    if (!DeviceManager::GetInstance().CheckDeviceFormated()) {
        return 0;
    }

    if (!cfgPara_.forceOverwrite) {
        HMFS_INFO("Use the -f option to force overwrite.");
        return -1;
    }

    auto buf = std::make_unique<uint8_t[]>(HMFS_BLOCK_SIZE);
    auto result = memset_s(buf.get(), HMFS_BLOCK_SIZE, 0, HMFS_BLOCK_SIZE);
    HMFS_CHECK(result == EOK, return -1, "Failed to memset buf");

    constexpr uint64_t OVERWRITE_BLOCK_COUNT = 1024;
    for (uint64_t blkId = 0; blkId < OVERWRITE_BLOCK_COUNT; blkId++) {
        if (HmfsIo::GetInstance().DevFillBlock(buf.get(), blkId)) {
            HMFS_ERROR("Failed to fill zeros at block id %" PRIu64 ".", blkId);
            return -1;
        }
    }
    if (HmfsIo::GetInstance().HmfsFsyncDevice()) {
        return -1;
    }

    return 0;
}

int32_t MkfsFormat::Format()
{
    superBlockFormater_ = std::make_unique<SuperBlockWriter>(*this);
    if (superBlockFormater_->Format() != 0) {
        return -1;
    }

    {
        auto sitFormater = std::make_unique<SitWriter>(*this);
        if (sitFormater->Format() != 0) {
            return -1;
        }
    }

    {
        auto natFormater = std::make_unique<NatWriter>(*this);
        if (natFormater->Format() != 0) {
            return -1;
        }
    }

    {
        auto mainFormater = std::make_unique<MainAreaWriter>(*this);
        if (mainFormater->Format() != 0) {
            return -1;
        }
    }

    {
        auto checkPointFormater = std::make_unique<CheckPointWriter>(*this);
        if (checkPointFormater->Format() != 0) {
            return -1;
        }
    }

    if (HmfsIo::GetInstance().HmfsFinalizeDevice() != 0) {
        return -1;
    }

    return 0;
}

SuperBlockData *MkfsFormat::GetSuperBlockData()
{
    return (superBlockFormater_ == nullptr) ? nullptr : &superBlockFormater_->superBlock_->superBlock;
}

} // namespace Hmfs
} // namespace OHOS
